#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
串口协议实现
集成现有的串口通信和协议解析功能
"""

import serial
import threading
import time
import logging
import re
import os
import base64
from enum import Enum
from datetime import datetime
from typing import Optional, Callable, Dict, Any
from PyQt6.QtCore import QThread, pyqtSignal

logger = logging.getLogger(__name__)

class AnalysisMode(Enum):
    """分析模式枚举"""
    UNKNOWN = 0
    NORMAL = 1          # 常规模式
    VARIANT_THAL = 2    # 变异/地贫模式
    FAST_VARIANT = 3    # 快速变异模式

class SerialPort:
    """串口通信类"""
    
    def __init__(self, port: str, baudrate: int = 115200):
        self._serial = None
        self._port = port
        self._baudrate = baudrate
        self._bytesize = serial.EIGHTBITS
        self._parity = serial.PARITY_NONE
        self._stopbits = serial.STOPBITS_ONE
        self._timeout = 1
        
        self._recv_thread = _SerialRecvThread()
        self._recv_thread.start()
        
        logger.info(f"串口对象初始化: {port}@{baudrate}")

    @property
    def port(self) -> str:
        return self._port
    
    @property
    def baudrate(self) -> int:
        return self._baudrate

    def is_open(self) -> bool:
        return self._serial.is_open if self._serial else False

    def open(self) -> bool:
        try:
            if self._serial and self.is_open():
                self.close()

            self._serial = serial.Serial(
                port=self._port,
                baudrate=self._baudrate,
                bytesize=self._bytesize,
                parity=self._parity,
                stopbits=self._stopbits,
                timeout=self._timeout
            )

            self._recv_thread.set_serial(self._serial)
            self._recv_thread.resume()
            logger.info(f"串口 {self._port} 打开成功")
            return True
        except serial.SerialException as e:
            logger.error(f"打开串口 {self._port} 失败: {e}")
            return False

    def close(self):
        if self._recv_thread:
            self._recv_thread.pause()
            self._recv_thread.stop()

        if self._serial and self._serial.is_open:
            self._serial.close()
            logger.info(f"串口 {self._port} 已关闭")

    def write(self, data: bytes) -> bool:
        if self.is_open():
            try:
                self._serial.write(data)
                return True
            except serial.SerialException as e:
                logger.error(f"发送数据失败: {e}")
        return False

    def register_on_read(self, callback: Callable[[bytes], None]):
        self._recv_thread.set_callback(callback)

    def test_connection(self) -> bool:
        try:
            if not self.is_open():
                return False
            
            test_data = b'\x00'
            self._serial.write(test_data)
            return True
        except Exception as e:
            logger.error(f"测试串口连接失败: {e}")
            return False

class _SerialRecvThread(threading.Thread):
    """串口数据接收线程类"""
    
    def __init__(self):
        super().__init__()
        self._serial = None
        self._callback = None
        self._running = False
        self._paused = True
        self.daemon = True

    def set_serial(self, serial_obj):
        self._serial = serial_obj

    def set_callback(self, callback):
        self._callback = callback

    def resume(self):
        self._paused = False

    def pause(self):
        self._paused = True

    def stop(self):
        self._running = False

    def run(self):
        self._running = True
        while self._running:
            if not self._paused and self._serial and self._serial.is_open:
                try:
                    if self._serial.in_waiting > 0:
                        data = self._serial.read(self._serial.in_waiting)
                        if data and self._callback:
                            self._callback(data)
                except Exception as e:
                    logger.error(f"串口数据接收异常: {e}")
                    time.sleep(0.1)
            else:
                time.sleep(0.1)

class SerialCommunicationThread(QThread):
    """串口通信线程"""
    
    data_received = pyqtSignal(bytes)
    connection_status = pyqtSignal(bool)
    message_processed = pyqtSignal(dict)
    
    def __init__(self, serial_port: SerialPort, data_callback=None, comm_manager=None):
        super().__init__()
        self.serial_port = serial_port
        self.data_callback = data_callback
        self.comm_manager = comm_manager
        self.running = False
        self.buffer = b''
        self.max_buffer_size = 1024 * 1024  # 1MB
        
        self.parser = CustomLISParser()
        logger.info("串口通信线程初始化完成")

    def run(self):
        self.running = True
        self.serial_port.register_on_read(self.on_data_received)
        logger.info("串口通信线程已启动")
        
        while self.running:
            time.sleep(0.1)
        
        logger.info("串口通信线程已停止")

    def stop(self):
        self.running = False
        logger.info("串口通信线程停止信号已发送")

    def on_data_received(self, data: bytes):
        try:
            if not self.running:
                return
            
            self.buffer += data
            
            if len(self.buffer) > self.max_buffer_size:
                logger.warning("串口缓冲区过大，清空缓冲区")
                self.buffer = data
            
            self.data_received.emit(data)
            self.try_parse_message()
            
        except Exception as e:
            logger.error(f"处理串口数据失败: {e}")

    def try_parse_message(self):
        try:
            if b'<TRANSMIT>' in self.buffer and b'</TRANSMIT>' in self.buffer:
                start_idx = self.buffer.find(b'<TRANSMIT>')
                end_idx = self.buffer.find(b'</TRANSMIT>') + len(b'</TRANSMIT>')
                
                if start_idx < end_idx:
                    message_data = self.buffer[start_idx:end_idx]
                    parsed_message = self.parser.parse_message(message_data)
                    
                    if parsed_message:
                        self.message_processed.emit(parsed_message)
                        
                        if self.data_callback:
                            self.data_callback(parsed_message)
                        
                        logger.info(f"串口消息解析成功: {parsed_message.get('sample_info', {}).get('sample_no', 'Unknown')}")
                    
                    self.buffer = self.buffer[end_idx:]
                    
        except Exception as e:
            logger.error(f"解析串口消息失败: {e}")

class CustomLISParser:
    """自定义LIS协议解析器"""
    
    @staticmethod
    def parse_message(data: bytes) -> Optional[Dict[str, Any]]:
        try:
            if b'<TRANSMIT>' not in data:
                return None

            message = {
                'protocol': 'CUSTOM_LIS',
                'machine_model': None,
                'machine_id': None,
                'sample_info': {},
                'results': {},
                'image': {},
                'analysis_mode': AnalysisMode.UNKNOWN,
                'mode_info': {},
                'raw': data.decode('utf-8', errors='ignore'),
                'receive_time': datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
            }

            if '<TRANSMIT>' in message['raw'] and '|' in message['raw'] and '<M>' not in message['raw']:
                CustomLISParser.parse_new_format(message)
            else:
                CustomLISParser.parse_old_format(message)

            return message
            
        except Exception as e:
            logger.error(f"解析串口消息失败: {e}")
            return None

    @staticmethod
    def parse_new_format(message: Dict[str, Any]):
        """解析新格式的协议"""
        try:
            raw_data = message['raw']
            
            start_tag = '<TRANSMIT>'
            end_tag = '</TRANSMIT>'
            
            start_idx = raw_data.find(start_tag) + len(start_tag)
            end_idx = raw_data.find(end_tag)
            
            if start_idx == -1 or end_idx == -1:
                return
            
            main_content = raw_data[start_idx:end_idx]
            parts = main_content.split('|')
            
            clean_parts = []
            for part in parts:
                if '<TIME>' in part:
                    time_start = part.find('<TIME>')
                    if time_start > 0:
                        clean_parts.append(part[:time_start])
                    break
                elif '<AREA>' in part:
                    area_start = part.find('<AREA>')
                    if area_start > 0:
                        clean_parts.append(part[:area_start])
                    break
                elif '<IMAGE>' in part:
                    image_start = part.find('<IMAGE>')
                    if image_start > 0:
                        clean_parts.append(part[:image_start])
                    break
                else:
                    clean_parts.append(part)
            
            if len(clean_parts) >= 3:
                message['machine_model'] = clean_parts[0].strip()
                message['machine_id'] = clean_parts[1].strip()
                
                if len(clean_parts) >= 3:
                    message['sample_info']['datetime'] = clean_parts[2].strip()
                
                if len(clean_parts) >= 4:
                    message['sample_info']['sample_no'] = clean_parts[3].strip()
                
                if len(clean_parts) >= 5:
                    message['sample_info']['sample_id'] = clean_parts[4].strip()
                
                if len(clean_parts) >= 6:
                    message['sample_info']['position'] = clean_parts[5].strip()
                
                if len(clean_parts) >= 7:
                    message['sample_info']['sample_type'] = clean_parts[6].strip()
                
                # 解析检测结果
                results_start = 7
                for i in range(results_start, len(clean_parts)):
                    part = clean_parts[i].strip()
                    if part.startswith('<TIME>'):
                        break
                    
                    if len(part) > 3 and part[0].isalpha():
                        known_patterns = [
                            r'^HbA0(\d+\.?\d*)$',
                            r'^p3(\d+\.?\d*)$',
                            r'^A2(\d+\.?\d*)$',
                            r'^([A-Za-z0-9]+?)(\d+\.?\d*)$',
                        ]
                        
                        parsed = False
                        for pattern in known_patterns:
                            match = re.match(pattern, part)
                            if match:
                                if pattern.startswith('^HbA0'):
                                    item_name = 'HbA0'
                                    item_value = match.group(1)
                                elif pattern.startswith('^p3'):
                                    item_name = 'p3'
                                    item_value = match.group(1)
                                elif pattern.startswith('^A2'):
                                    item_name = 'A2'
                                    item_value = match.group(1)
                                else:
                                    item_name = match.group(1)
                                    item_value = match.group(2)
                                
                                message['results'][item_name] = item_value
                                parsed = True
                                break
                        
                        if not parsed:
                            for j in range(len(part) - 1, -1, -1):
                                if part[j].isalpha():
                                    item_name = part[:j+1]
                                    item_value = part[j+1:]
                                    break
                            
                            if item_name and item_value:
                                message['results'][item_name] = item_value
                
                # 检测分析模式
                results_str = "|".join([f"{k}|{v}" for k, v in message['results'].items() if k not in ['TIME', 'AREA', 'TIME_DETAILS', 'AREA_DETAILS']])
                if results_str:
                    mode, mode_name = CustomLISParser.detect_analysis_mode(results_str)
                    message['analysis_mode'] = mode
                    message['mode_info'] = CustomLISParser.get_mode_description(mode)
                
                # 解析TIME数据
                time_match = re.search(r'<TIME>(.*?)</TIME>', raw_data)
                if time_match:
                    time_data = time_match.group(1)
                    time_parts = time_data.split('|')
                    message['results']['TIME'] = time_parts
                    
                    time_mapping = {
                        0: 'HbA1a出峰时间（秒）',
                        1: 'HbA1b出峰时间（秒）',
                        2: 'HbF出峰时间（秒）',
                        3: 'LA1c出峰时间（秒）',
                        4: 'HbA1c出峰时间（秒）',
                        5: 'HbA0出峰时间（秒）',
                        6: 'P3出峰时间（秒）',
                        7: 'v-win出峰时间（秒）',
                        8: 'A2出峰时间（秒）',
                        9: 'HbE出峰时间（秒）',
                        10: 'HbD出峰时间（秒）',
                        11: 'HbS出峰时间（秒）',
                        12: 'HbC出峰时间（秒）'
                    }
                    
                    time_details = {}
                    for i, time_value in enumerate(time_parts):
                        if i in time_mapping:
                            time_details[time_mapping[i]] = time_value
                        else:
                            time_details[f'未知项目{i}出峰时间（秒）'] = time_value
                    
                    message['results']['TIME_DETAILS'] = time_details
                
                # 解析AREA数据
                area_match = re.search(r'<AREA>(.*?)</AREA>', raw_data)
                if area_match:
                    area_data = area_match.group(1)
                    area_parts = area_data.split('|')
                    message['results']['AREA'] = area_parts
                    
                    area_mapping = {
                        0: 'HbA1a面积',
                        1: 'HbA1b面积',
                        2: 'HbF面积',
                        3: 'LA1c面积',
                        4: 'HbA1c面积',
                        5: 'HbA0面积',
                        6: 'P3面积',
                        7: 'v-win面积',
                        8: 'A2面积',
                        9: 'HbE面积',
                        10: 'HbD面积',
                        11: 'HbS面积',
                        12: 'HbC面积',
                        13: '总面积'
                    }
                    
                    area_details = {}
                    for i, area_value in enumerate(area_parts):
                        if i in area_mapping:
                            area_details[area_mapping[i]] = area_value
                        else:
                            area_details[f'未知项目{i}面积'] = area_value
                    
                    message['results']['AREA_DETAILS'] = area_details

                # 添加样本类型映射
                sample_type_map = {
                    '0': '全血样品',
                    '1': '质控样品', 
                    '2': '定标样品',
                    '3': '稀释样品',
                    '4': '急诊样品'
                }
                
                if 'sample_type' in message['sample_info']:
                    sample_type_code = message['sample_info']['sample_type']
                    if sample_type_code in sample_type_map:
                        message['sample_info']['sample_type_name'] = sample_type_map[sample_type_code]

            # 提取图片信息
            image_match = re.search(r'<IMAGE>(.*?)</IMAGE>', raw_data, re.DOTALL)
            if image_match:
                image_content = image_match.group(1)
                
                name_match = re.search(r'<NAME>(.*?)</NAME>', image_content)
                if name_match:
                    message['image']['filename'] = name_match.group(1)
                
                size_match = re.search(r'<SIZE>(.*?)</SIZE>', image_content)
                if size_match:
                    message['image']['size'] = int(size_match.group(1))
                
                data_match = re.search(r'<DATA>(.*?)</DATA>', image_content, re.DOTALL)
                if data_match:
                    image_data = data_match.group(1).strip()
                    message['image']['data'] = image_data
                    message['image']['data_length'] = len(image_data)
                    
                    if message['image']['filename'] and message['image']['data']:
                        try:
                            if CustomLISParser.is_hex_data(image_data):
                                save_result = CustomLISParser.save_hex_image(
                                    image_data, 
                                    message['image']['filename']
                                )
                                if save_result:
                                    message['image'].update(save_result)
                                    message['image']['saved'] = True
                                    message['image']['format'] = 'hex_to_binary'
                                    logger.info(f"十六进制图片保存成功，路径: {save_result['path']}")
                                else:
                                    message['image']['saved'] = False
                                    message['image']['error'] = "保存十六进制图片失败"
                            else:
                                save_result = CustomLISParser.save_base64_image(
                                    image_data, 
                                    message['image']['filename']
                                )
                                if save_result:
                                    message['image'].update(save_result)
                                    message['image']['saved'] = True
                                    message['image']['format'] = 'base64_to_binary'
                                    logger.info(f"Base64图片保存成功，路径: {save_result['path']}")
                                else:
                                    message['image']['saved'] = False
                                    message['image']['error'] = "保存Base64图片失败"
                                
                        except Exception as e:
                            message['image']['saved'] = False
                            message['image']['error'] = str(e)
                            logger.error(f"图片处理异常: {e}")
                            
        except Exception as e:
            logger.error(f"解析新格式协议失败: {e}")

    @staticmethod
    def parse_old_format(message: Dict[str, Any]):
        """解析旧格式的协议"""
        try:
            raw_data = message['raw']
            
            # 提取机器信息
            machine_patterns = [
                r'<M>(.*?)<I>',
                r'<M>(.*?)</M>',
                r'<M>(.*?)(?:<I>|</I>|</TRANSMIT>)',
            ]
            
            for pattern in machine_patterns:
                machine_match = re.search(pattern, raw_data)
                if machine_match:
                    machine_data = machine_match.group(1).strip()
                    
                    if '|' in machine_data:
                        machine_parts = machine_data.split('|')
                        if len(machine_parts) >= 2:
                            message['machine_model'] = machine_parts[0].strip()
                            message['machine_id'] = machine_parts[1].strip()
                            break
                    else:
                        message['machine_model'] = machine_data
                        break

            # 提取样本信息
            sample_patterns = [
                r'<I>(.*?)</I>',
                r'<I>(.*?)(?:<R>|</TRANSMIT>)',
            ]
            
            for pattern in sample_patterns:
                sample_match = re.search(pattern, raw_data)
                if sample_match:
                    sample_data = sample_match.group(1).strip()
                    
                    if '|' in sample_data:
                        sample_parts = sample_data.split('|')
                        if len(sample_parts) >= 4:
                            message['sample_info']['datetime'] = sample_parts[0].strip()
                            message['sample_info']['sample_no'] = sample_parts[1].strip()
                            message['sample_info']['sample_id'] = sample_parts[2].strip()
                            message['sample_info']['position'] = sample_parts[3].strip()
                            
                            if len(sample_parts) >= 5:
                                message['sample_info']['sample_type'] = sample_parts[4].strip()
                    break

            # 提取检测结果
            results_match = re.search(r'<R>(.*?)</R>', raw_data)
            if results_match:
                results_data = results_match.group(1).strip()
                
                if '|' in results_data:
                    results_parts = results_data.split('|')
                    for part in results_parts:
                        part = part.strip()
                        if '=' in part:
                            key, value = part.split('=', 1)
                            message['results'][key.strip()] = value.strip()
                        elif len(part) > 3 and part[0].isalpha():
                            for i in range(len(part) - 1, -1, -1):
                                if part[i].isalpha():
                                    item_name = part[:i+1]
                                    item_value = part[i+1:]
                                    if item_name and item_value:
                                        message['results'][item_name] = item_value
                                    break

            # 检测分析模式
            results_str = "|".join([f"{k}|{v}" for k, v in message['results'].items() if k not in ['TIME', 'AREA', 'TIME_DETAILS', 'AREA_DETAILS']])
            if results_str:
                mode, mode_name = CustomLISParser.detect_analysis_mode(results_str)
                message['analysis_mode'] = mode
                message['mode_info'] = CustomLISParser.get_mode_description(mode)
                
        except Exception as e:
            logger.error(f"解析旧格式协议失败: {e}")

    @staticmethod
    def detect_analysis_mode(results_str: str) -> tuple:
        """检测分析模式"""
        try:
            if 'v-win' in results_str:
                return AnalysisMode.FAST_VARIANT, '快速变异模式'
            
            if any(item in results_str for item in ['A2', 'HbE', 'HbD', 'HbS', 'HbC']):
                return AnalysisMode.VARIANT_THAL, '变异/地贫模式'
            
            if any(item in results_str for item in ['HbA1a', 'HbA1b', 'HbF', 'LA1c', 'HbA1c', 'HbA0', 'eAG']):
                return AnalysisMode.NORMAL, '常规模式'
            
            return AnalysisMode.UNKNOWN, '未知模式'
            
        except Exception as e:
            logger.error(f"检测分析模式失败: {e}")
            return AnalysisMode.UNKNOWN, '未知模式'

    @staticmethod
    def get_mode_description(mode: AnalysisMode) -> dict:
        """获取分析模式描述"""
        descriptions = {
            'NORMAL': {
                'name': '常规模式',
                'description': '标准HbA1c检测（7项）',
                'typical_fields': ['HbA1a', 'HbA1b', 'HbF', 'LA1c', 'HbA1c', 'HbA0', 'eAG']
            },
            'VARIANT_THAL': {
                'name': '变异/地贫模式',
                'description': '扩展检测（13项），包含变异血红蛋白和地贫筛查',
                'typical_fields': ['HbA1a', 'HbA1b', 'HbF', 'LA1c', 'HbA1c', 'A2', 'HbE', 'HbD', 'HbS', 'HbC', 'HbA0', 'p3', 'eAG']
            },
            'FAST_VARIANT': {
                'name': '快速变异模式',
                'description': '字段简化（9项），但含特有标记v-win（快速识别变异窗口）',
                'typical_fields': ['HbA1a', 'HbA1b', 'HbF', 'L-A1c', 'HbA1c', 'p3', 'HbA0', 'v-win', 'eAG']
            },
            'UNKNOWN': {
                'name': '未知模式',
                'description': '无法识别的分析模式',
                'typical_fields': []
            }
        }
        
        mode_name = mode.name if hasattr(mode, 'name') else 'UNKNOWN'
        return descriptions.get(mode_name, descriptions['UNKNOWN'])

    @staticmethod
    def is_hex_data(data: str) -> bool:
        """判断是否为十六进制数据"""
        try:
            clean_data = data.replace('\n', '').replace('\r', '').replace(' ', '')
            return all(c in '0123456789ABCDEFabcdef' for c in clean_data)
        except Exception:
            return False

    @staticmethod
    def save_hex_image(hex_data: str, filename: str, images_dir: str = 'images') -> Optional[Dict[str, Any]]:
        """保存十六进制图片数据"""
        try:
            if not os.path.exists(images_dir):
                os.makedirs(images_dir)
            
            clean_hex_data = hex_data.replace('\n', '').replace('\r', '').replace(' ', '')
            img_bytes = bytes.fromhex(clean_hex_data)
            
            image_path = os.path.join(images_dir, filename)
            with open(image_path, 'wb') as f:
                f.write(img_bytes)
            
            return {
                'path': image_path,
                'size': len(img_bytes),
                'hex_length': len(clean_hex_data),
                'format': 'hex_to_binary'
            }
                
        except Exception as e:
            logger.error(f"保存十六进制图片失败: {e}")
            return None

    @staticmethod
    def save_base64_image(base64_data: str, filename: str, images_dir: str = 'images') -> Optional[Dict[str, Any]]:
        """保存Base64图片数据"""
        try:
            if not os.path.exists(images_dir):
                os.makedirs(images_dir)
            
            clean_base64_data = base64_data.replace('\n', '').replace('\r', '').replace(' ', '')
            img_bytes = base64.b64decode(clean_base64_data)
            
            image_path = os.path.join(images_dir, filename)
            with open(image_path, 'wb') as f:
                f.write(img_bytes)
            
            return {
                'path': image_path,
                'size': len(img_bytes),
                'base64_length': len(clean_base64_data),
                'format': 'base64_to_binary'
            }
                
        except Exception as e:
            logger.error(f"保存Base64图片失败: {e}")
            return None
